﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Automapping;
using System.Configuration;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.AcceptanceCriteria;
using FluentNHibernate.Conventions.Instances;
using FluentNHibernate.Conventions.Inspections;
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Conventions.Helpers;
using System.Reflection;

namespace Subtle.Data.Configuration
{
	/// <summary>
	/// Session Factory Builder
	/// </summary>
	public class SessionFactoryBuilder
	{
		private Configuration configuration;

		private static SessionFactoryBuilder instance;
		private static ISessionFactory sessionFactory;

		/// <summary>
		/// Current Session
		/// </summary>
		public static ISessionFactory SessionFactory
		{
			get
			{
				if (instance == null)
				{
					instance = new SessionFactoryBuilder();
				}
				if (sessionFactory == null)
				{
					sessionFactory = instance.CreateSessionFactory();
				}
				return sessionFactory;
			}
		}

		/// <summary>
		/// Initializes a new Session Factory Builder
		/// </summary>
		private SessionFactoryBuilder()
		{
			configuration = new Configuration();
		}

		/// <summary>
		/// Create Session Factory
		/// </summary>
		/// <returns></returns>
		public ISessionFactory CreateSessionFactory()
		{
			NHibernate.Cfg.Environment.UseReflectionOptimizer = false;
			if (ConfigurationManager.AppSettings["DBConnectionString"] == null)
			{
				throw new ConfigurationErrorsException("The AppSettings Key [DBConnectionString] does not exist in the Application Settings");
			}

			if (ConfigurationManager.AppSettings["AssembliesToMap"] == null)
			{
				throw new ConfigurationErrorsException("The AppSettings Key [AssembliesToMap] does not exist in the Application Settings");
			}

			return Fluently.Configure()
				.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromAppSetting("DBConnectionString")))
				.Mappings(mappings => mappings.AutoMappings.Add(AutoMap.Assemblies(configuration, (from assembly in AppDomain.CurrentDomain.GetAssemblies()
																								   where assembly.FullName.Contains(ConfigurationManager.AppSettings["AssembliesToMap"])
																								   select assembly).ToArray()).OverrideAll((map)=>map.IgnoreProperties(property
																									=> property.DeclaringType.GetProperty(property.Name).GetCustomAttributes(typeof(IgnoreAutoMappingAttribute), true).Length > 0))
				.Conventions.Add(new IConvention[] { DefaultCascade.SaveUpdate(), new StringColumnLengthConvention(), new ByteColumnLengthConvention() })))
				.ExposeConfiguration(UpdateSchema)
				.BuildSessionFactory();
		}

		/// <summary>
		/// Update the Schema
		/// </summary>
		/// <param name="configuration"></param>
		private static void UpdateSchema(NHibernate.Cfg.Configuration configuration)
		{
			var autopersistence = new AutoPersistenceModel();
			autopersistence.Configure(configuration);
			configuration.SetProperty("current_session_context_class", "web");
			new SchemaUpdate(configuration).Execute(true, true);
		}
	}

	/// <summary>
	/// String Column Length Convention
	/// </summary>
	public class StringColumnLengthConvention : IPropertyConvention, IPropertyConventionAcceptance
	{
		public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
		{
			criteria.Expect(x => x.Type == typeof(string)).Expect(x => x.Length == 0);
		}
		public void Apply(IPropertyInstance instance)
		{
			instance.Length(10000);
		}
	}

	/// <summary>
	/// Byte Column Length Convention
	/// </summary>
	public class ByteColumnLengthConvention : IPropertyConvention, IPropertyConventionAcceptance
	{
		public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
		{
			criteria.Expect(x => x.Type == typeof(byte[])).Expect(x => x.Length == 0);
		}
		public void Apply(IPropertyInstance instance)
		{
			instance.Length(30000000);
		}
	}


	/// <summary>
	/// Sumomo Configuration
	/// </summary>
	public class Configuration : DefaultAutomappingConfiguration
	{
		private string namespaceToMap;

		/// <summary>
		/// Initializes a new Configuration
		/// </summary>
		public Configuration()
		{
			if (ConfigurationManager.AppSettings["NamespaceToMap"] == null)
			{
				throw new ConfigurationErrorsException("The AppSettings Key [NamespaceToMap] does not exist in the Application Settings");
			}
			namespaceToMap = ConfigurationManager.AppSettings["NamespaceToMap"];
		}

		/// <summary>
		/// Should Map
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public override bool ShouldMap(Type type)
		{
			return type.Namespace == namespaceToMap;
		}

		/// <summary>
		/// Is ID
		/// </summary>
		/// <param name="member"></param>
		/// <returns></returns>
		public override bool IsId(FluentNHibernate.Member member)
		{
			return member.Name == "ID";
		}
	}
}
